use std::{path::PathBuf, fs::{File, self}, io::{Write, Read}};

use quote::ToTokens;

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

pub fn write_file(string: &str, project_path: &std::ffi::OsString, ref_path: &str) {
    let mut path_buf = PathBuf::from(project_path);
    path_buf.push(ref_path);

    let generate_path = path_buf.as_path();

    if let Ok(mut generate_file) = File::create(generate_path) {
        generate_file
            .write_all(string.as_bytes())
            .expect(format!("[Generate][Error] Failed to Write({:?})", generate_path).as_str());
    } else {
        println!("[Generate][Warning] Failed to Create({:?})", generate_path);
    }
}

pub fn parse_file(project_path: &std::ffi::OsString, ref_path: &str) -> Result<syn::File, Box<dyn std::error::Error>> {
    // Load Code
    let mut path = PathBuf::from(project_path);
    path.push(ref_path);
    let mut code = String::new();
    match File::open(&path) {
        Ok(mut file) => 
            file.read_to_string(&mut code).expect(format!("[Generate][Error] Failed to Read({:?})", path).as_str()),

        Err(err) => {
            println!("[Generate][Error] Failed to Open({:?})", path);
            return Err(Box::new(err));
        }
    };

    let ast = syn::parse_file(code.as_str())?;
    Ok(ast)
}

macro_rules! USE_STRUCT_FORMAT {
    () => {
r#"
use {};
"#
    };
}

macro_rules! START_IMPL_FORMAT {
    () => {
r#"
impl {} {{ "#
    };
}

macro_rules! END_IMPL_FORMAT {
    () => {
r#"
}
"#
    };
}

macro_rules! GET_STRUCT_NAME_FORMAT {
    () => {
r#"
    pub fn reflection_get_struct_name(&self) -> &str {{ 
        "{}" 
    }}
"#
    };
}

macro_rules! GET_FIELD_NAME_FORMAT {
    () => {
r#"
    pub fn reflection_get_field_name_{}(&self) -> &str {{ 
        "{}"
    }}
"#
    };
}

macro_rules! GET_FIELD_TYPE_NAME_FORMAT {
    () => {
r#"
    pub fn reflection_get_field_type_name_{}(&self) -> &str {{ 
        "{}"
    }}
"#
    };
}

macro_rules! SET_FORMAT {
    () => {
r#"
    pub fn reflection_set_{}(&mut self, input: {}) {{ 
        self.{} = input;
    }}
"#
    };
}

macro_rules! GET_FORMAT {
    () => {
r#"
    pub fn reflection_get_{}(&self) -> &{} {{ 
        &self.{}
    }}
"#
    };
}

macro_rules! GET_ELEMENT_TYPE_NAME_FORMAT {
    () => {
r#"
    pub fn reflection_get_element_type_name_{}(&self) -> &str {{
        "{}"
    }}
"#
    };
}

macro_rules! GET_ARRAY_SIZE_FORMAT {
    () => {
r#"
    pub fn reflection_get_array_size_{}(&self) -> usize {{
        self.{}.len()
    }}
"#
    };
}

macro_rules! SET_ELEMENT_FORMAT {
    () => {
r#"
    pub fn reflection_set_element_{}(&mut self, index: usize, input: {}) {{
        self.{}[index] = input;
    }}
"#
    };
}

macro_rules! GET_ELEMENT_FORMAT {
    () => {
r#"
    pub fn reflection_get_element_{}(&self, index: usize) -> &{} {{
        &self.{}[index]
    }}
"#
    };
}

macro_rules! ADD_ELEMENT_FORMAT {
    () => {
r#"
    pub fn reflection_add_element_{}(&mut self, input: {}) {{
        self.{}.push(input);
    }}
"#
    };
}

fn generate_reflection(item: &syn::ItemStruct, mod_file: &mut Vec<String>, crate_list: &mut Vec<String>, use_string: &String, project_path: &std::ffi::OsString) {
    let mut output = String::from("");
    let struct_name = item.ident.to_string();

    // Begin
    output.push_str(use_string.as_str());
    crate_list.push(item.ident.to_string());
    output.push_str(format!(USE_STRUCT_FORMAT!(), crate_list.join("::")).as_str());
    crate_list.pop();

    output.push_str(format!(START_IMPL_FORMAT!(), struct_name).as_str());

    // Inner
    output.push_str(format!(GET_STRUCT_NAME_FORMAT!(), struct_name).as_str());
    for field in &item.fields {
        // Get Value Define
        let field_name = field.ident.as_ref().unwrap();
        let field_name_string = field_name.to_string();
        
        let field_type_name_string = field.ty.to_token_stream().to_string().replace(" ", "");
        
        output.push_str(format!(GET_FIELD_NAME_FORMAT!(), field_name_string, field_name_string).as_str());
        output.push_str(format!(GET_FIELD_TYPE_NAME_FORMAT!(), field_name_string, field_type_name_string).as_str());
        output.push_str(format!(SET_FORMAT!(), field_name_string, field_type_name_string, field_name_string).as_str());
        output.push_str(format!(GET_FORMAT!(), field_name_string, field_type_name_string, field_name_string).as_str());

        // Array
        if let syn::Type::Path(path_type) = &field.ty {
            let outer_type_name = &path_type.path.segments[0].ident;

            if outer_type_name.to_string().eq("Vec") {
                let element_type_name = match &path_type.path.segments[0].arguments {
                    syn::PathArguments::AngleBracketed(arguments) => 
                        arguments.args.to_token_stream().to_string().replace(" ", ""),

                    syn::PathArguments::Parenthesized(_) =>
                        String::from("None"),

                    &syn::PathArguments::None =>
                        String::from("None")
                };

                output.push_str(format!(GET_ELEMENT_TYPE_NAME_FORMAT!(), field_name_string, element_type_name).as_str());
                output.push_str(format!(GET_ARRAY_SIZE_FORMAT!(), field_name_string, field_name_string).as_str());
                output.push_str(format!(SET_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
                output.push_str(format!(GET_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
                output.push_str(format!(ADD_ELEMENT_FORMAT!(), field_name_string, element_type_name, field_name_string).as_str());
            }
        }
    }

    // End
    output.push_str(END_IMPL_FORMAT!());
    println!("[Generate][Debug] generate_reflection: {}", output);

    // Output
    mod_file.push(String::from(String::from("pub mod ") + &struct_name + ";"));
    write_file(output.as_str(), project_path, format!("./src/_generated/{}.rs", struct_name).as_str())

}

fn collect_use_item(ast: &syn::File, crate_list: &mut Vec<String>) -> String {
    let mut use_string = String::new();

    for item in &ast.items {
        match item {
            syn::Item::Use(use_item) => {
                println!("{:?} && {:?}", crate_list.join("::"), use_item.to_token_stream().to_string());
                if use_item.to_token_stream().to_string().contains("super") {
                    // println!("{:#?}", use_item);
                }
                
                use_string.push_str("use ");
                use_string.push_str(use_item.tree.to_token_stream().to_string().replace(" ", "").as_str());
                use_string.push_str(";\n");
            },
            _ => {}
        }
    }

    use_string
}

pub fn generate_file(project_path: &std::ffi::OsString, mod_file: &mut Vec<String>, crate_list: &mut Vec<String>, path_str: &str) {
    if let Ok(ast) = &parse_file(project_path, path_str) {
        // pre collect [use] statement
        let use_string = collect_use_item(ast, crate_list);

        for item in &ast.items {
            match item {
                syn::Item::Struct(struct_item) => {
                    // if need to 
                    let is_need_deal;
                    is_need_deal = struct_item.attrs.iter().any(
                        |attr|
                        if let syn::Meta::Path(path) = &attr.meta {
                            path.segments.iter().any(
                                |segment|
                                segment.ident.to_string().eq("reflection")
                            )
                        } else {
                            false
                        }
                    );
                    // run 
                    if is_need_deal {
                        generate_reflection(struct_item, mod_file, crate_list, &use_string, project_path);
                    }
                },
                _ => {},
            }
        }
    }   
}

pub fn visit_dir(dir: &std::path::Path, mod_file: &mut Vec<String>, crate_list: &mut Vec<String>, ref_path: &mut String, project_path: &std::ffi::OsString) {
    // 迭代遍历文件夹
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries {
            // 如果条目无效跳过
            if let Ok(dir_entry) = entry {
                let entry_path = dir_entry.path();
                // 如果是文件夹
                if entry_path.is_dir() {
                    let crate_name = String::from(entry_path.file_name().unwrap().to_str().unwrap());
                    let mut ref_path_new = ref_path.to_string() + &String::from("/") + &crate_name;

                    crate_list.push(crate_name);
                    visit_dir(&entry_path, mod_file, crate_list, &mut ref_path_new, project_path);
                    crate_list.pop();

                } else {
                    // 筛选一下
                    let path_str = entry_path.to_str().unwrap();
                    if path_str.ends_with(".rs") {
                        let crate_name = String::from(entry_path.file_name().unwrap().to_str().unwrap());

                        let ref_file_path = ref_path.to_string() + &String::from("/") + &crate_name;

                        crate_list.push(crate_name[..crate_name.len()-3].to_string()); // remove ".rs"
                        generate_file(project_path, mod_file, crate_list, ref_file_path.as_str());
                        crate_list.pop();

                        // Debug
                        // mod_file.push(ref_file_path.to_string());
                        // mod_file.push(String::from(path_string));
                    }
                }
            }
        }
    }
}

pub fn run(project_path: &std::ffi::OsString) {
    println!("[Generate][Info] In Path({:?})", project_path);
    // Path Check
    let mut dir_path = PathBuf::from(project_path);
    dir_path.push("./src/_generated");
    if std::fs::metadata(&dir_path).is_ok() {
        fs::remove_dir_all(&dir_path).expect("");
    }

    return;
    
    fs::create_dir(&dir_path)
        .expect(format!("[Generate][Warning] Failed to Create Folder({:?})", dir_path).as_str());

    // Recursive Find File From [src] folder
    let mut mod_file = Vec::<String>::new();

    let mut crate_list = Vec::<String>::new();
    crate_list.push(String::from("crate"));

    let mut dir_path = PathBuf::from(project_path);
    dir_path.push("./src");
    visit_dir(&std::path::Path::new(&dir_path), &mut mod_file, &mut crate_list, &mut String::from("./src"), project_path);

    // Output mod.rs
    let output = mod_file.join("\n");
    write_file(output.as_str(), project_path, "./src/_generated/mod.rs");
}
